#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "riscv.h" // 使用riscv.h前要include types.h, 否则报`error: unknown type name 'uint64'`
#include "spinlock.h"
#include "proc.h"
#include "defs.h"

struct cpu cpus[NCPU];

struct proc proc[NPROC];

int nextpid = 1;
struct spinlock pid_lock;

static void freeproc(struct proc *p);

// helps ensure that wakeups of wait()ing
// parents are not lost. helps obey the
// memory model when using p->parent.
// must be acquired before any p->lock.
struct spinlock wait_lock;

// Allocate a page for each process's kernel stack.
// Map it high in memory, followed by an invalid
// guard page.
// 进行进程内核栈的映射, 效果是:
//  +-------------------max(dram)--------------------+
//  |                                                |
//  +-------------------TRAMPOLINE-------------------+
//  |                                                |
//  +-------------------guard page-------------------+
//  |                                                |
//  +-------------------kernel stack------------------+
//  |                                                |
//  +-------------------guard page-------------------+
//  |                                                |
//  +-------------------kernel stack-----------------+
//                       ....
void
proc_mapstacks(pagetable_t kpgtbl)
{
  struct proc *p;

  for(p = proc; p < &proc[NPROC]; p++) {
    char *pa = kalloc();
    if(pa == 0)
      panic("kalloc");

    // p - proc 计算出当前进程 p 在 proc 数组中的索引
    // 在 C 语言中，当对两个相同类型的指针进行相减运算时，结果并不是简单的字节地址之差，而是它们之间相隔的元素数量
    // 设置guard page的原因: 因为guard page的PTE是非法的(没有设置PTE_V), 如果进程kernel栈溢出, 会触发异常
    uint64 va = KSTACK((int) (p - proc));
    kvmmap(kpgtbl, va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
  }
}

// initialize the proc table.
void
procinit(void)
{
  struct proc *p;

  initlock(&pid_lock, "nextpid");
  initlock(&wait_lock, "wait_lock");
  for(p = proc; p < &proc[NPROC]; p++) {
      initlock(&p->lock, "proc");
      p->state = UNUSED;
      p->kstack = KSTACK((int) (p - proc)); // 为每个进程分配内核栈
  }
}

// Must be called with interrupts disabled,
// to prevent race with process being moved
// to a different CPU.
int
cpuid()
{
  int id = r_tp();
  return id;
}

// Return this CPU's cpu struct.
// Interrupts must be disabled.
struct cpu*
mycpu(void)
{
  int id = cpuid();
  struct cpu *c = &cpus[id];
  return c;
}

// Return the current struct proc *, or zero if none.
struct proc*
myproc(void)
{
  push_off();
  struct cpu *c = mycpu();
  struct proc *p = c->proc;
  pop_off();
  return p;
}


int
allocpid()
{
  int pid;
  
  acquire(&pid_lock);
  pid = nextpid;
  nextpid = nextpid + 1;
  release(&pid_lock);

  return pid;
}

// Look in the process table for an UNUSED proc.
// If found, initialize state required to run in the kernel,
// and return with p->lock held.
// If there are no free procs, or a memory allocation fails, return 0.
// 分配进程资源
static struct proc*
allocproc(void)
{
  struct proc *p;

  for(p = proc; p < &proc[NPROC]; p++) {
    acquire(&p->lock);
    if(p->state == UNUSED) {
      goto found;
    } else {
      release(&p->lock);
    }
  }
  return 0;

found:
  p->pid = allocpid();
  p->state = USED;

  // Allocate a trapframe page.
  // trapframe 用于用户态和内核态之间上下文的安全切换
  if((p->trapframe = (struct trapframe *)kalloc()) == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }

  // Set up new context to start executing at forkret,
  // which returns to user space.
  memset(&p->context, 0, sizeof(p->context));
  p->context.sp = p->kstack + PGSIZE;

  return p;
}

// free a proc structure and the data hanging from it,
// including user pages.
// p->lock must be held.
static void
freeproc(struct proc *p)
{
  if(p->trapframe)
    kfree((void*)p->trapframe);
  p->trapframe = 0;
  if(p->pagetable)
    //proc_freepagetable(p->pagetable, p->sz);
  p->pagetable = 0;
  p->sz = 0;
  p->pid = 0;
  p->parent = 0;
  p->name[0] = 0;
  p->chan = 0;
  p->killed = 0;
  p->xstate = 0;
  p->state = UNUSED;
}


// Per-CPU process scheduler.
// Each CPU calls scheduler() after setting itself up.
// Scheduler never returns.  It loops, doing:
//  - choose a process to run.
//  - swtch to start running that process.
//  - eventually that process transfers control
//    via swtch back to the scheduler.
/*
[scheduler()]
  |
  |--> acquire(p->lock)
  |--> p->state = RUNNING
  |--> swtch(&scheduler_ctx, &p->context)
                        |
                        v
                  [process p runs on same CPU]
                        |
                  yield():
                        |
                  acquire(p->lock) -- succeeds because same CPU
                        |
                  p->state = RUNNABLE
                  sched() -- swtch back to scheduler

*/
void
scheduler(void)
{
  struct proc *p;
  struct cpu *c = mycpu();

  c->proc = 0; // 表示当前核心没有正在运行的进程，处于空闲状态
  for(;;){
    // The most recent process to run may have had interrupts
    // turned off; enable them to avoid a deadlock if all
    // processes are waiting. Then turn them back off
    // to avoid a possible race between an interrupt
    // and wfi.
    intr_on();
    intr_off();

    int found = 0;
    for(p = proc; p < &proc[NPROC]; p++) {
      acquire(&p->lock);
      if(p->state == RUNNABLE) { // 如果进程处于 RUNNABLE 状态，表示它可以被调度器运行
        // Switch to chosen process.  It is the process's job
        // to release its lock and then reacquire it
        // before jumping back to us.
        p->state = RUNNING;
        c->proc = p;

        swtch(&c->context, &p->context);

        // Process is done running for now.
        // It should have changed its p->state before coming back.
        c->proc = 0;
        found = 1;
      }
      release(&p->lock);
    }
    if(found == 0) {
      // nothing to run; stop running on this core until an interrupt.
      // 执行 wfi (Wait For Interrupt) 汇编指令，让 CPU 进入低功耗睡眠状态，直到一个中断发生. 这可以节省能源并避免不必要的忙等
      asm volatile("wfi");
    }
  }
}

// Switch to scheduler.  Must hold only p->lock
// and have changed proc->state. Saves and restores
// intena because intena is a property of this
// kernel thread, not this CPU. It should
// be proc->intena and proc->noff, but that would
// break in the few places where a lock is held but
// there's no process.
void
sched(void)
{
  int intena;
  struct proc *p = myproc();

  if(!holding(&p->lock))
    panic("sched p->lock");
  if(mycpu()->noff != 1)
    panic("sched locks");
  if(p->state == RUNNING)
    panic("sched RUNNING");
  if(intr_get())
    panic("sched interruptible");

  intena = mycpu()->intena;
  swtch(&p->context, &mycpu()->context);
  mycpu()->intena = intena;
}

// Give up the CPU for one scheduling round.
// yield() 函数会自愿放弃 CPU，将当前进程的状态设置为 RUNNABLE，并调用 scheduler()，让其他进程有机会运行
void
yield(void)
{
  struct proc *p = myproc();

  if (!p->is_kernel){
    acquire(&p->lock);
  }

  p->state = RUNNABLE;
  sched();

  if (!p->is_kernel){
    release(&p->lock);
  }
}

// 延迟函数：模拟sleep, 空循环消耗 CPU 时间，控制打印频率
void
mock_sleep(void)
{
  int t = 0;
  for(volatile int i = 0; i < 100000000; i++){
    t++;
  }
}

// 第一个进程的执行体，打印 'a'
void
proc_a(void)
{
    for (;;) {
        printf("a\n");
        mock_sleep();
        yield();
    }
}

// 第二个进程的执行体，打印 'b'
void
proc_b(void)
{
    for (;;) {
        printf("b\n");
        mock_sleep();
        yield();
    }
}

void
kproinit1(void)
{
  struct proc *p;

  p = allocproc();
  p->context.ra = (uint64)proc_a;
  p->is_kernel = 1;  // 标记为内核进程

  //p->cwd = namei("/");

  p->state = RUNNABLE;
  safestrcpy(p->name, "kproinit1",sizeof(p->name));

  release(&p->lock);
}

void
kproinit2(void)
{
  struct proc *p;

  p = allocproc();
  p->context.ra = (uint64)proc_b;
  p->is_kernel = 1;  // 标记为内核进程

  //p->cwd = namei("/");

  p->state = RUNNABLE;
  safestrcpy(p->name, "kproinit2",sizeof(p->name));

  release(&p->lock);
}
